-- Classes implementation in Lua
-- Bits shamelessly ripped off from the lua-users.org wiki

--[[
    Todo:
        * Better introspection
        * Proxy objects
        * Posing
        * Better support for delegation

]]

require 'util'

classes = {}

-- Create a new class that inherits from a base class
--
function class( className, baseClass )

    -- If this class already exists, error out
    if classes[className] ~= nil then
        error("Class " .. className .. " already exists")
    end

    local new_class = {}
    local class_mt = {}

    -- Set up the class metatable
    class_mt.__index = new_class

    -- Put this class in the registry
    classes[className] = new_class

    -- Inject this name into the global namespace so we can refer to it directly
    -- This has the effect of being able to go class("Foo"); f = Foo:create()
    _G[className] = new_class

    -- Factory function for this new class
    function new_class:create(...)
        local newinst = {}
        setmetatable( newinst, class_mt )
        newinst:_construct(...)
        return newinst
    end

    -- We have a base class? Sweet, delegate all failed lookups to it
    if nil ~= baseClass then
        setmetatable( new_class, { __index = baseClass } )
    end

    -- Implementation of additional OO properties starts here --

    -- Return the class object of the instance
    function new_class:class()
        return new_class
    end

    -- Generic constructor
    function new_class:_construct()
        -- do nothing
        --print (className .. " constructed")
    end

    -- Return the string version of the class's name
    function new_class:className()
        return className
    end

    -- Return the super class object of the instance
    function new_class:superClass()
        return baseClass
    end

    -- Return true if this class has the specified function
    function new_class:hasFunction(func)
        if self[func] ~= nil then
            -- We have this value, let's make sure it's a function
            if type(self[func]) == "function" then
                return true
            end
        end
        return false
    end
    
    -- Set a delegate for this object
    function new_class:setDelegate(newDelegate)
        self._delegate = newDelegate
    end
    
    -- Calls a function in the delegate, if it has one
    function new_class:delegate(functionName, ...)
        if self._delegate == nil then return nil end
        
        if self._delegate:hasFunction(functionName) then
            return self._delegate[functionName](self._delegate, ...)
        end
        return nil
        
    end

    -- Intercept all lookups for this object, if we are able to
    -- Not yet working
    function new_class:becomeProxyFor(object)
        error("becomeProxyFor is not implemented")
        if object == nil then
            error ("Tried to become proxy for a nil object")
        end
        setmetatable(object,
            {__index =
                function ( key )
                    print ("hai")
                    return nil
                end
            })
        return object
    end

    -- Return true if the caller is an instance of theClass or an instance of a subclass of theClass
    function new_class:isa( theClass )
        local b_isa = false

        local cur_class = new_class

        while ( nil ~= cur_class ) and ( false == b_isa ) do
            if cur_class == theClass then
                b_isa = true
            else
                cur_class = cur_class:superClass()
            end
        end

        return b_isa
    end

    function new_class:getMembers( )
        local members = {}

        for key,value in pairs(self) do
            table.insert(members, key)
        end

        for key,value in pairs(new_class) do
            table.insert(members, key)
        end

        if baseClass ~= nil then
            local baseMembers = baseClass:getMembers()
            for i,v in ipairs(baseMembers) do
                table.insert(members, v)
            end
        end

        -- Remove duplicates
        members = table_unique(members)

        return members
    end

    function new_class:getIvars( )
        local members = self:getMembers()
        local ivars = {}
        for i,v in ipairs(members) do
            if type(self[v]) ~= "function" then
                table.insert(ivars, v)
            end
        end
        return ivars
    end

    function new_class:getFunctions( )
        local members = self:getMembers()
        local funcs = {}
        for i,v in ipairs(members) do
            if type(self[v]) == "function" then
                table.insert(funcs, v)
            end
        end
        return funcs
    end

    return new_class
end

-- Returns a class with a given name
function classNamed(className)
    local c = classes[className]
    if c == nil then
        error("Class " .. className .. " does not exist")
    end
    return c
end

-- Define a root class to work with
class("Object")
